package com.ocean;

import java.util.List;
import java.rmi.RemoteException;//ServiceException
import java.util.zip.CRC32;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.Date;

import com.ocean.common.exception.ClosetoOverException;
import com.ocean.exception.LeaderException;
import com.ocean.util.LogUtil;

public class ParkService extends MementoService implements Park {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1645605281792671781L;
	private ParkObjValue parkinfo = new ParkObjValue();
	private static ObjValue hbinfo = new ObjValue();
	// private Lock lk = new ReentrantLock();
	private ReadWriteLock rwlk = new ReentrantReadWriteLock();
	private ParkLeader pl = null;

	public ParkService(String host, int port, String[][] servers,
			String parkService) throws RemoteException {
		pl = new ParkLeader(host, port, servers, parkService);
		pl.wantBeMaster(this);
	}

	private String checkSessionId(String sessionid) {
		if (sessionid == null)
			return "se" + System.nanoTime();
		else
			return sessionid;
	}

	@SuppressWarnings("rawtypes")
	public Long updateDomainVersion(String domain) throws RemoteException {
		Long domainversion = (Long) parkinfo.getObj(ParkMeta.getYBB(domain));

		ObjValue nodeversions = parkinfo.getWidely(ParkMeta.getYBB(domain
				+ "\\..+"));
		List vlist = nodeversions.getObjValues();
		String crcstr = "";
		for (Object obj : vlist)
			crcstr += obj + "";

		Long crcversion = getObjectVersion(ObjectBytes.toBytes(crcstr));
		return (crcversion != domainversion) ? crcversion : domainversion;
	}

	private Long updateDomainVersion() {
		Long crcversion = getObjectVersion(ObjectBytes.toBytes(System
				.nanoTime()));
		return crcversion;
	}

	private Long getObjectVersion(byte[] obj) {
		CRC32 crc = new CRC32();
		crc.update(obj);// ObjectBytes.toBytes(o)
		return new Long(crc.getValue());
	}

	@Override
	public String getSessionId() throws RemoteException {
		return checkSessionId(null);
	}

	@Override
	public ObjValue create(String domain, String node, byte[] obj,
			String sessionid, int auth, boolean heartbeat)
			throws RemoteException, ClosetoOverException {
		ClosetoOverException.checkMemCapacity();
		ObjValue objv = null;
		if (domain != null && node != null) {
			// lk.lock();
			Lock wlk = rwlk.writeLock();
			wlk.lock();
			try {
				String domainnodekey = ParkObjValue.getDomainnodekey(domain,
						node);

				if (!parkinfo.containsKey(domainnodekey)) {
					if (!parkinfo.containsKey(domain)) {
						parkinfo.setObj(domain, 0l);
						parkinfo.setObj(ParkMeta.getYBB(domain), 0l);
						parkinfo.setString(ParkMeta.getYCJZ(domain), sessionid);
						parkinfo.setString(ParkMeta.getYCIP(domain),
								getClientHost());
						parkinfo.setObj(ParkMeta.getYCSJ(domain),
								System.currentTimeMillis());
					}
					parkinfo.setObj(domainnodekey, obj);
					parkinfo.setObj(ParkMeta.getYBB(domainnodekey),
							getObjectVersion(obj));
					parkinfo.setObj(ParkMeta.getYBB(domain),
							updateDomainVersion());// updateDomainVersion(domain)
					parkinfo.setObj(ParkMeta.getYCJZ(domainnodekey), sessionid);
					parkinfo.setObj(ParkMeta.getYQX(domainnodekey), auth);
					parkinfo.setObj(ParkMeta.getYCIP(domainnodekey),
							getClientHost());
					parkinfo.setObj(ParkMeta.getYCSJ(domainnodekey),
							System.currentTimeMillis());
					Long nodenum = (Long) parkinfo.getObj(domain);
					parkinfo.setObj(domain, nodenum + 1);

					if (heartbeat) {
						parkinfo.setObj(ParkMeta.getYSX(domainnodekey),
								ParkMeta.getSXXT());
						// HbDaemo.runGetTask(hbinfo, this);
					}

					pl.runCopyTask(domainnodekey, this);
					LogUtil.fine("[create]", "[" + domainnodekey + "]", obj);

					objv = get(domain, node, sessionid);
				} else
					LogUtil.info("[Park]", "[create]", domainnodekey
							+ " is exist!");// throw exist exception
			} catch (Exception e) {
				// e.printStackTrace();
				LogUtil.info("[Park]", "[create]", e);
			} finally {
				// lk.unlock();
				wlk.unlock();
			}
		}
		return objv;
	}

	// synchronized
	@Override
	public ObjValue update(String domain, String node, byte[] obj,
			String sessionid) throws RemoteException, ClosetoOverException {
		ClosetoOverException.checkMemCapacity();
		ObjValue objv = null;
		if (domain != null && node != null) {
			// lk.lock();
			Lock wlk = rwlk.writeLock();
			wlk.lock();
			try {
				String domainnodekey = ParkObjValue.getDomainnodekey(domain,
						node);
				if (checkAuth(domainnodekey, sessionid,
						AuthPolicy.OP_READ_WRITE)) {
					if (parkinfo.containsKey(domainnodekey)) {
						parkinfo.setObj(domainnodekey, obj);
						Long theversion = getObjectVersion(obj);
						if (theversion != (Long) parkinfo.getObj(ParkMeta
								.getYBB(domainnodekey))) {
							parkinfo.setObj(ParkMeta.getYBB(domainnodekey),
									theversion);
							parkinfo.setObj(ParkMeta.getYBB(domain),
									updateDomainVersion());// updateDomainVersion(domain)
						}
						parkinfo.setString(ParkMeta.getYGXZ(domainnodekey),
								sessionid);
						parkinfo.setString(ParkMeta.getYGIP(domainnodekey),
								getClientHost());
						parkinfo.setObj(ParkMeta.getYGSJ(domainnodekey),
								System.currentTimeMillis());

						LogUtil.fine("[update]", "[" + domainnodekey + "]", obj);
						pl.runCopyTask(domainnodekey, this);
						objv = get(domain, node, sessionid);
					} else
						LogUtil.info("[Park]", "[update]", domainnodekey
								+ " is not exist!");// throw not exist exception
				}
			} catch (Exception e) {
				// e.printStackTrace();
				LogUtil.info("[Park]", "[update]", e);
			} finally {
				// lk.unlock();
				wlk.unlock();
			}
		}
		return objv;
	}

	@Override
	public boolean update(String domain, int auth, String sessionid)
			throws RemoteException {
		boolean updateflag = false;
		if (domain != null) {
			Lock wlk = rwlk.writeLock();
			wlk.lock();
			try {
				if (parkinfo.containsKey(domain)) {
					if (checkAuth(domain, sessionid, AuthPolicy.OP_READ_WRITE)) {
						parkinfo.setObj(ParkMeta.getYQX(domain), auth);
						updateflag = true;
						LogUtil.fine("[update]", "[" + domain + " Auth]", auth);
						pl.runCopyTask(domain, this);
					}
				} else
					LogUtil.info("[Park]", "[update]", domain
							+ " is not exist!");// throw not exist exception
			} catch (Exception e) {
				LogUtil.info("[Park]", "[update]", e);
			} finally {
				wlk.unlock();
			}
		}
		return updateflag;
	}

	@Override
	public ObjValue delete(String domain, String node, String sessionid)
			throws RemoteException, ClosetoOverException {
		ObjValue objrm = null;
		// if(sessionid==Acl)
		if (domain != null) {
			if (node == null)
				ClosetoOverException.checkMemCapacity();

			if (checkAuth(ParkObjValue.getDomainnodekey(domain, node),
					sessionid, AuthPolicy.OP_ALL))
				objrm = delete(domain, node);
		}
		return objrm;
	}

	protected ObjValue delete(String domain, String node) {
		ObjValue objrm = null;
		Lock wlk = rwlk.writeLock();
		wlk.lock();
		try {
			String domainnodekey = ParkObjValue.getDomainnodekey(domain, node);
			objrm = parkinfo.removeNode(domain, node);// removeNodeWidely(domainnodekey);
			if (!objrm.isEmpty()) {
				Long nodenum = (Long) parkinfo.getObj(domain);
				// System.out.println("delete nodenum:"+nodenum);
				if (nodenum != null) {
					if (nodenum == 1l)
						parkinfo.removeDomain(domain);// removeNodeWidely(domain);
					else {
						parkinfo.setObj(domain, nodenum - 1);
						parkinfo.setObj(ParkMeta.getYBB(domain),
								updateDomainVersion());// updateDomainVersion(domain)
					}
				}
				LogUtil.fine("[delete]", "[" + domainnodekey + "]", objrm);
				pl.runCopyTask(domainnodekey, this);
			} else {
				objrm = null;
				LogUtil.info("[Park]", "[delete]", domainnodekey
						+ " cant be deleted or not exist!");// throw not exist
															// exception
			}
		} catch (Exception e) {
			// e.printStackTrace();
			LogUtil.info("[Park]", "[delete]", e);
		} finally {
			wlk.unlock();
		}
		return objrm;
	}

	public boolean checkAuth(String domainnodekey, String sessionid,
			AuthPolicy targetauth) {
		boolean authflag = false;
		String creator = parkinfo.getString(ParkMeta.getYCJZ(domainnodekey));
		if (creator != null) {
			if (creator.equals(sessionid))
				authflag = true;
			else {
				Object domainnodeAuth = parkinfo.getObj(ParkMeta
						.getYQX(domainnodekey));
				int thekeyauth = domainnodeAuth != null ? (Integer) domainnodeAuth
						: 1;// only read for domain
				authflag = AuthPolicy.authIncluded(targetauth.getPolicy(),
						thekeyauth);
			}
		}
		if (!authflag)
			LogUtil.info("[Park]", "[AuthPolicy]", "No permissions to do for "
					+ domainnodekey + "!");
		return authflag;
	}

	@Override
	public ObjValue get(String domain, String node, String sessionid)
			throws RemoteException, ClosetoOverException {
		// String thesessionid = checkSessionId(sessionid);
		// lk.lock();
		ObjValue ov = null;
		if (domain != null) {
			if (node == null)
				ClosetoOverException.checkMemCapacity();

			Lock rlk = rwlk.readLock();
			rlk.lock();
			LogUtil.fine("[Park]", "[get]",
					ParkObjValue.getDomainnodekey(domain, node));
			ov = parkinfo.getNode(domain, node);// parkinfo.getNodeWidely(parkinfo.getDomainnodekey(domain,
												// node));
			if (ov.isEmpty())
				ov = null;
			// lk.unlock();
			rlk.unlock();
		}
		return ov;
	}

	@Override
	public ObjValue getLastest(String domain, String node, String sessionid,
			long version) throws RemoteException, ClosetoOverException {
		// lk.lock();
		Lock rlk = rwlk.readLock();
		rlk.lock();
		Long nodeversion = (Long) parkinfo.getObj(ParkMeta.getYBB(ParkObjValue
				.getDomainnodekey(domain, node)));
		if (nodeversion != null && nodeversion != version)
			LogUtil.fine("[Park]", "[getLastest]", "nodeversion:" + nodeversion
					+ ";version:" + version);
		ObjValue ov = (nodeversion != null && nodeversion != version) ? get(
				domain, node, sessionid) : null;
		// lk.unlock();
		rlk.unlock();
		return ov;
	}

	@Override
	public ObjValue getParkinfo() throws RemoteException {
		try {
			LogUtil.fine("[Park]", "[getParkinfo]", "getParkinfo from "
					+ getClientHost());
		} catch (Exception e) {
			LogUtil.fine("[Park]", "[getParkinfo]", e.getMessage());
		}
		return getTheParkinfo();
	}

	ObjValue getTheParkinfo() {
		ObjValue ov = null;
		Lock rlk = rwlk.readLock();
		rlk.lock();
		ov = parkinfo.getParkInfo();
		rlk.unlock();
		return ov;
	}

	@Override
	public boolean setParkinfo(ObjValue ov) throws RemoteException {
		LogUtil.fine("[Park]", "[setParkinfo]", ov);
		Lock wlk = rwlk.writeLock();
		wlk.lock();
		parkinfo = (ParkObjValue) ov;
		wlk.unlock();
		return true;
	}

	@Override
	public String[] askMaster() throws RemoteException {
		LogUtil.info("[Park]", "[askMaster]",
				"receive askMaster................");
		return pl.isMaster();
	}

	@Override
	public boolean askLeader() throws RemoteException, LeaderException {
		LogUtil.info("[Park]", "[askLeader]",
				"receive askLeader................");
		String[] sv = new String[2];
		if (pl.checkMasterPark(sv, this))
			return true;
		else
			throw new LeaderException(pl.getThisserver(), sv);
	}

	@Override
	public boolean heartbeat(String[] domainnodekey, String sessionid)
			throws RemoteException {
		boolean hbback = false;
		if (domainnodekey != null) {
			for (String curkey : domainnodekey)
				hbinfo.setObj(curkey, new Date().getTime());
			hbback = true;
		}
		// System.out.println("hbinfo:"+hbinfo);
		HbDaemo.runGetTask(hbinfo, this);
		return hbback;
	}

	/*
	 * public List<ObjValue> getNodesInDomain(String domain, String sessionid)
	 * throws RemoteException { return null; }
	 */
	public static void main(String[] args) {
		if (args != null && args.length == 2)
			BeanContext.startPark(args[0], Integer.parseInt(args[1]));
		else
			BeanContext.startPark();
		/*
		 * BeanContext.setConfigFile("D:\\demo\\comutil\\test\\config.xml");
		 * String[][] servers = new String[args.length][]; for(int
		 * i=0;i<args.length;i++){ String[] theserver = new
		 * String[]{"localhost",args[i]}; servers[i] = theserver; } try{
		 * ParkService ps = new
		 * ParkService(servers[0][0],Integer.parseInt(servers[0][1]), servers,
		 * "ParkService"); Long i=new Long(0); System.out.println(i+","+new
		 * java.util.Date()); while(true) { ObjValue ov = ps.createTest("d",
		 * i+"", new byte[1], "aaaaa", 7, false); if(i%100==0) {
		 * System.out.println(i+","+new java.util.Date()); } i++; }
		 * }catch(Exception e){ System.out.println(e); }
		 */
		// BeanContext.startPark(servers[0][0],Integer.parseInt(servers[0][1]),
		// servers);
		// BeanContext.startPark();
	}
}